#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Reflection;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// Definitions of built in Yarr functions
	/// </summary>
	internal static partial class Functions
	{
		/// <summary>
		/// (add number*)
		/// Returns the sum of all the specified numbers.
		/// Each argument must be a numeric type, such as System.In32 or System.Double
		/// </summary>
		/// <param name="args">The numbers to be added</param>
		/// <param name="environment">The local environment</param>
		/// <returns>The sum of the parameters. The returned number type is dependent on the arguments</returns>
        [Function("+", "(&rest args)")]
        public static Object Add(Environment environment) 
		{
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null)
                return 0;

            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType;
            TypeCode bType;

            Object result = args.First();

            if (args.Length == 1)
            {
                if (!Primitives.IsNumber(result))
                {
                    t = new Type[1];
                    t[0] = Primitives.TypeOf(result);

                    mi = MethodCache.FindMethod("op_unaryplus", t[0], t, null,
                                            BindingFlags.IgnoreCase |
                                                    BindingFlags.Public |
                                                    BindingFlags.Static |
                                                    BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "Add: No unary plus operator defined for type {0}", t[0]));

                    a = new Object[1];
                    a[0] = result;

                    return mi.Invoke(t[0], a);
                }
                else
                    return result;
            }
            
            foreach (Object number in (Cons)args.Rest()) 
			{
                aType = result == null ? TypeCode.Empty : Type.GetTypeCode(Primitives.TypeOf(result));
                bType = Type.GetTypeCode(Primitives.TypeOf(number));

                if (result == null)
                    result = number;

                else if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                    result = AddHelper(result, number, aType, bType);
                else
                {
                    Type t0 = Primitives.TypeOf(result);
                    Type t1 = Primitives.TypeOf(number);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_addition", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "Add: No addition operator defined for ({0},{1})", t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = result;
                    a[1] = number;

                    result = mi.Invoke(t[0], a);
                }
			}

			return result;
		}

        internal static Object AddHelper (object a, object b, TypeCode aType, TypeCode bType)
        {
            try
            {
                if (aType == TypeCode.Double || bType == TypeCode.Double)
                    return (Double)Convert.ChangeType(a, typeof(Double)) + 
                           (Double)Convert.ChangeType(b, typeof(Double));

                else if (aType == TypeCode.Single || bType == TypeCode.Single)
                    return (Single)Convert.ChangeType(a, typeof(Single)) + 
                           (Single)Convert.ChangeType(b, typeof(Single));

                else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                    return (Decimal)Convert.ChangeType(a, typeof(Decimal)) +
                           (Decimal)Convert.ChangeType(b, typeof(Decimal));

                else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                    return (UInt64)Convert.ChangeType(a, typeof(UInt64)) + 
                           (UInt64)Convert.ChangeType(b, typeof(UInt64));

                else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                    return (Int64)Convert.ChangeType(a, typeof(Int64)) + 
                           (Int64)Convert.ChangeType(b, typeof(Int64));

                else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                    return (UInt32)Convert.ChangeType(a, typeof(UInt32)) + 
                           (UInt32)Convert.ChangeType(b, typeof(UInt32));

                else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                    return (Int32)Convert.ChangeType(a, typeof(Int32)) + 
                           (Int32)Convert.ChangeType(b, typeof(Int32));

                else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                    return (UInt16)((UInt32)Convert.ChangeType(a, typeof(UInt32)) + 
                                    (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Char || bType == TypeCode.Char)
                    return (Char)((UInt32)Convert.ChangeType(a, typeof(UInt32)) + 
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                    return (Int16)((Int32)Convert.ChangeType(a, typeof(Int32)) + 
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                    return (Byte)((UInt32)Convert.ChangeType(a, typeof(UInt32)) + 
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                    return (SByte)((Int32)Convert.ChangeType(a, typeof(Int32)) +
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else
                    throw new YarrException("Add: arguments must be numeric");
            }
            catch (System.InvalidCastException)
            {
                throw new YarrException("Add: arguments must be numeric");
            }
        }


        
        /// <summary>
		/// (/ numerator denominator+) 
		/// Divides a numerator by one or more denominators.
        /// Each argument must be a numeric type, such as System.In32 or System.Double
        /// </summary>
		/// <param name="args">A list of numbers</param>
		/// <param name="environment">The local environment</param>
		/// <returns>The first number divided by the product of the rest of the numbers.
        /// The return type is dependent on the arguments</returns>
        [Function("/", "(arg0 &rest args)")]
        public static Object Divide(Environment environment)
        {
            object result = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType;
            TypeCode bType  = Type.GetTypeCode(Primitives.TypeOf(result));

            if (args == null)
                return DivideHelper(1m, result, TypeCode.Decimal, bType);

            foreach (Object number in args)
            {
                aType = Type.GetTypeCode(Primitives.TypeOf(result));
                bType = Type.GetTypeCode(Primitives.TypeOf(number));

                if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                    result = DivideHelper(result, number, aType, bType);
                else
                {
                    Type t0 = Primitives.TypeOf(result);
                    Type t1 = Primitives.TypeOf(number);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_division", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "Divide: No division operator defined for ({0},{1})", t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = result;
                    a[1] = number;

                    result = mi.Invoke(t[0], a);
                }
            }

            return result;
        }

        private static Object DivideHelper(object a, object b, TypeCode aType, TypeCode bType)
        {
            try
            {
                if (aType == TypeCode.Double || bType == TypeCode.Double)
                    return (Double)Convert.ChangeType(a, typeof(Double)) / 
                           (Double)Convert.ChangeType(b, typeof(Double));

                else if (aType == TypeCode.Single || bType == TypeCode.Single)
                    return (Single)Convert.ChangeType(a, typeof(Single)) / 
                           (Single)Convert.ChangeType(b, typeof(Single));

                else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                    return (Decimal)Convert.ChangeType(a, typeof(Decimal)) /
                           (Decimal)Convert.ChangeType(b, typeof(Decimal));

                else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                    return (UInt64)Convert.ChangeType(a, typeof(UInt64)) / 
                           (UInt64)Convert.ChangeType(b, typeof(UInt64));

                else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                    return (Int64)Convert.ChangeType(a, typeof(Int64)) / 
                           (Int64)Convert.ChangeType(b, typeof(Int64));

                else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                    return (UInt32)Convert.ChangeType(a, typeof(UInt32)) / 
                           (UInt32)Convert.ChangeType(b, typeof(UInt32));

                else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                    return (Int32)Convert.ChangeType(a, typeof(Int32)) / 
                           (Int32)Convert.ChangeType(b, typeof(Int32));

                else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                    return (UInt16)((UInt32)Convert.ChangeType(a, typeof(UInt32)) /
                                    (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Char || bType == TypeCode.Char)
                    return (Char)((UInt32)Convert.ChangeType(a, typeof(UInt32)) /
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                    return (Int16)((Int32)Convert.ChangeType(a, typeof(Int32)) /
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                    return (Byte)((UInt32)Convert.ChangeType(a, typeof(UInt32)) /
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                    return (SByte)((Int32)Convert.ChangeType(a, typeof(Int32)) /
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else
                    throw new YarrException("Divide: arguments must be numeric");
            }
            catch (System.InvalidCastException)
            {
                throw new YarrException("Divide: arguments must be numeric");
            }
        }


		/// <summary>
		/// (&amp; expression*)
		/// Performs a bitwise and operation on its arguments
		/// </summary>
		/// <param name="args">A list of numbers to AND together</param>
		/// <param name="environment">The local environment</param>
		/// <returns>The bitwise AND of its arguments</returns>
		[Function("&", "(arg0 &rest args)")]
        public static Object BitwiseAnd(Environment environment) 
		{
            object a = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null)
                return a;

            MethodInfo mi = null;
            Type[] t = null;
            Object[] arg = null;
            TypeCode aType;
            TypeCode bType;

            foreach (Object b in args)
            {
                aType = a == null ? TypeCode.Empty : Type.GetTypeCode(Primitives.TypeOf(a));
                bType = Type.GetTypeCode(Primitives.TypeOf(b));

                if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                {
                    try
                    {
                        if (aType == TypeCode.Double || bType == TypeCode.Double || 
                            aType == TypeCode.Decimal || bType == TypeCode.Decimal || 
                            aType == TypeCode.Single || bType == TypeCode.Single)
                            throw new YarrException("BitwiseAnd: Function is only defined for integral number types");

                        if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                            a = (UInt64)Convert.ChangeType(a, typeof(UInt64)) & 
                                (UInt64)Convert.ChangeType(b, typeof(UInt64));

                        else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                            a = (Int64)Convert.ChangeType(a, typeof(Int64)) & 
                                (Int64)Convert.ChangeType(b, typeof(Int64));

                        else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                            a = (UInt32)Convert.ChangeType(a, typeof(UInt32)) & 
                                (UInt32)Convert.ChangeType(b, typeof(UInt32));

                        else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                            a = (Int32)Convert.ChangeType(a, typeof(Int32)) & 
                                (Int32)Convert.ChangeType(b, typeof(Int32));

                        else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                            a = (UInt16)((UInt32)Convert.ChangeType(a, typeof(UInt32)) &
                                         (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.Char || bType == TypeCode.Char)
                            a = (Char)((UInt32)Convert.ChangeType(a, typeof(UInt32)) &
                                       (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                            a = (Int16)((Int32)Convert.ChangeType(a, typeof(Int32)) &
                                        (Int32)Convert.ChangeType(b, typeof(Int32)));

                        else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                            a = (Byte)((UInt32)Convert.ChangeType(a, typeof(UInt32)) &
                                       (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                            a = (SByte)((Int32)Convert.ChangeType(a, typeof(Int32)) &
                                        (Int32)Convert.ChangeType(b, typeof(Int32)));

                        else
                            throw new YarrException("BitwiseAnd: arguments must be numeric");
                    }
                    catch (System.InvalidCastException)
                    {
                        throw new YarrException("BitwiseAnd: arguments must be numeric");
                    }
                }

                else
                {
                    Type t0 = Primitives.TypeOf(a);
                    Type t1 = Primitives.TypeOf(b);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_bitwiseand", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "BitwiseAnd: No bitwise-and operator defined for ({0},{1})", t[0], t[1]));

                    if (arg == null)
                        arg = new Object[2];

                    arg[0] = a;
                    arg[1] = b;

                    a = mi.Invoke(t[0], arg);
                }
            }

            return a;
        }


		/// <summary>
		/// (| expression*)
		/// Performs a bitwise inclusive or operation on its arguments
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[Function("|", "(arg0 &rest args)")]
        public static Object BitwiseOr(Environment environment) 
		{
            object a = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null)
                return a;

            MethodInfo mi = null;
            Type[] t = null;
            Object[] arg = null;
            TypeCode aType;
            TypeCode bType  = TypeCode.Empty;

            foreach (Object b in args)
            {
                aType = a == null ? TypeCode.Empty : Type.GetTypeCode(Primitives.TypeOf(a));
                bType = Type.GetTypeCode(Primitives.TypeOf(b));
                
                if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                {
                    try
                    {
                        if (aType == TypeCode.Double || bType == TypeCode.Double || 
                            aType == TypeCode.Decimal || bType == TypeCode.Decimal || 
                            aType == TypeCode.Single || bType == TypeCode.Single)
                            throw new YarrException("BitwiseOr: Function is only defined for integral number types");

                        if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                            a = (UInt64)Convert.ChangeType(a, typeof(UInt64)) | 
                                (UInt64)Convert.ChangeType(b, typeof(UInt64));

                        else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                            a = (Int64)Convert.ChangeType(a, typeof(Int64)) | 
                                (Int64)Convert.ChangeType(b, typeof(Int64));

                        else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                            a = (UInt32)Convert.ChangeType(a, typeof(UInt32)) | 
                                (UInt32)Convert.ChangeType(b, typeof(UInt32));

                        else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                            a = (Int32)Convert.ChangeType(a, typeof(Int32)) | 
                                (Int32)Convert.ChangeType(b, typeof(Int32));

                        else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                            a = (UInt16)((UInt32)Convert.ChangeType(a, typeof(UInt32)) |
                                         (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.Char || bType == TypeCode.Char)
                            a = (Char)((UInt32)Convert.ChangeType(a, typeof(UInt32)) |
                                       (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                            a = (Int16)((Int32)Convert.ChangeType(a, typeof(Int32)) |
                                        (Int32)Convert.ChangeType(b, typeof(Int32)));

                        else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                            a = (Byte)((UInt32)Convert.ChangeType(a, typeof(UInt32)) |
                                       (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                            a = (SByte)((Int32)Convert.ChangeType(a, typeof(Int32)) |
                                        (Int32)Convert.ChangeType(b, typeof(Int32)));

                        else
                            throw new YarrException("BitwiseOr: arguments must be numeric");
                    }
                    catch (System.InvalidCastException)
                    {
                        throw new YarrException("BitwiseOr: arguments must be numeric");
                    }
                }

                else
                {
                    Type t0 = Primitives.TypeOf(a);
                    Type t1 = Primitives.TypeOf(b);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t0)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_bitwiseor", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "BitwiseOr: No bitwise-or operator defined for ({0},{1})", t[0], t[1]));

                    if (arg == null)
                        arg = new Object[2];

                    arg[0] = a;
                    arg[1] = b;

                    a = mi.Invoke(t[0], arg);
                }
            }

            return a;
		}

        /// <summary>
        /// (~ expression)
        /// Performs bitwise inversion on the (integer) result of the expression
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [Function("~", "(arg)")]
        public static object OnesComplement (Environment environment)
        {
            object a = environment.GetValue(SYSTEM, "arg");

            if (a == null)
                throw new YarrException("OnesComplement: Argument is null");

            MethodInfo mi = null;
            Type[] t = null;
            Object[] arg = null;

            switch (Type.GetTypeCode(Primitives.TypeOf(a)))
            {
                case TypeCode.SByte:
                    return ~(SByte)a;

                case TypeCode.Byte:
                    return ~(Byte)a;

                case TypeCode.Int16:
                    return ~(Int16)a;

                case TypeCode.UInt16:
                    return ~(UInt16)a;

                case TypeCode.Int32:
                    return ~(Int32)a;

                case TypeCode.UInt32:
                    return ~(UInt32)a;

                case TypeCode.Int64:
                    return ~(Int64)a;

                case TypeCode.UInt64:
                    return ~(UInt64)a;

                default:
                    t = new Type[1];
                    t[0] = Primitives.TypeOf(a);
                    mi = MethodCache.FindMethod("op_onescomplement", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "OnesComplement: No ones-complement operator defined for type {0}", t[0]));

                    arg = new Object[1];
                    arg[0] = a;
                    return mi.Invoke(t[0], arg);
            }
        }

		/// <summary>
		/// (^ expression*)
		/// Performs a bitwise exclusive or operation on its arguments
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[Function("^", "(arg0 &rest args)")]
        public static Object BitwiseXor(Environment environment) 
		{
            object a = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null)
                return a;

            MethodInfo mi = null;
            Type[] t = null;
            Object[] arg = null;
            TypeCode aType;
            TypeCode bType;

            foreach (Object b in args)
            {
                aType = a == null ? TypeCode.Empty : Type.GetTypeCode(Primitives.TypeOf(a));
                bType = Type.GetTypeCode(Primitives.TypeOf(b));

                if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                {
                    try
                    {
                        if (aType == TypeCode.Double || bType == TypeCode.Double || 
                            aType == TypeCode.Decimal || bType == TypeCode.Decimal || 
                            aType == TypeCode.Single || bType == TypeCode.Single)
                            throw new YarrException("BitwiseXor: Function is only defined for integral number types");

                        if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                            a = (UInt64)Convert.ChangeType(a, typeof(UInt64)) ^ 
                                (UInt64)Convert.ChangeType(b, typeof(UInt64));

                        else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                            a = (Int64)Convert.ChangeType(a, typeof(Int64)) ^ 
                                (Int64)Convert.ChangeType(b, typeof(Int64));

                        else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                            a = (UInt32)Convert.ChangeType(a, typeof(UInt32)) ^ 
                                (UInt32)Convert.ChangeType(b, typeof(UInt32));

                        else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                            a = (Int32)Convert.ChangeType(a, typeof(Int32)) ^ 
                                (Int32)Convert.ChangeType(b, typeof(Int32));

                        else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                            a = (UInt16)((UInt32)Convert.ChangeType(a, typeof(UInt32)) ^
                                         (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.Char || bType == TypeCode.Char)
                            a = (Char)((UInt32)Convert.ChangeType(a, typeof(UInt32)) ^
                                       (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                            a = (Int16)((Int32)Convert.ChangeType(a, typeof(Int32)) ^
                                        (Int32)Convert.ChangeType(b, typeof(Int32)));

                        else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                            a = (Byte)((UInt32)Convert.ChangeType(a, typeof(UInt32)) ^
                                       (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                        else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                            a = (SByte)((Int32)Convert.ChangeType(a, typeof(Int32)) ^
                                        (Int32)Convert.ChangeType(b, typeof(Int32)));

                        else
                            throw new YarrException("BitwiseXor: arguments must be numeric");
                    }
                    catch (System.InvalidCastException)
                    {
                        throw new YarrException("BitwiseXor: arguments must be numeric");
                    }
                }

                else
                {
                    Type t0 = Primitives.TypeOf(a);
                    Type t1 = Primitives.TypeOf(b);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_exclusiveor", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "ExclusiveOr: No exclusive-or operator defined for ({0},{1})", t[0], t[1]));

                    if (arg == null)
                        arg = new Object[2];

                    arg[0] = a;
                    arg[1] = b;

                    a = mi.Invoke(t[0], arg);
                }
            }

            return a;
        }


        /// <summary>
        /// (max number*)
        /// Find the max of a list of numbers
        /// </summary>
        /// <param name="args">a list of numbers</param>
        /// <param name="environment">The local environment</param>
        /// <returns>The max of the list of numbers</returns>
        [Function("max", "(arg0 &rest args)")]
        public static object Max (Environment environment)
        {
            object result = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null)
                return result;

            foreach (Object number in args)
            {
                try
                {
                    object a = result, b = number;

                    Type ta = Primitives.TypeOf(a);
                    Type tb = Primitives.TypeOf(b);
                    if (a is IComparable  &&  b is IComparable  &&  ta == tb)
                    {
                        result = ((IComparable)a).CompareTo(b) > 0 ? a : b;
                        continue;
                    }

                    TypeCode aType = Type.GetTypeCode(ta);
                    TypeCode bType = Type.GetTypeCode(tb);
                    if (!Primitives.IsNumber(aType) || !Primitives.IsNumber(bType))
                        throw new YarrException("Max: arguments must be numeric");

                    if (aType == TypeCode.Double || bType == TypeCode.Double)
                        result = ((Double)Convert.ChangeType(a, typeof(Double)) > 
                                    (Double)Convert.ChangeType(b, typeof(Double))) ? a : b;

                    else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                        result = ((Decimal)Convert.ChangeType(a, typeof(Decimal)) > 
                                    (Decimal)Convert.ChangeType(b, typeof(Decimal))) ? a : b;

                    else if (aType == TypeCode.Single || bType == TypeCode.Single)
                        result = ((Single)Convert.ChangeType(a, typeof(Single)) > 
                                    (Single)Convert.ChangeType(b, typeof(Single))) ? a : b;

                    else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                        result = ((UInt64)Convert.ChangeType(a, typeof(UInt64)) > 
                                    (UInt64)Convert.ChangeType(b, typeof(UInt64))) ? a : b;

                    else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                        result = ((Int64)Convert.ChangeType(a, typeof(Int64)) > 
                                    (Int64)Convert.ChangeType(b, typeof(Int64))) ? a : b;

                    else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                        result = ((UInt32)Convert.ChangeType(a, typeof(UInt32)) > 
                                    (UInt32)Convert.ChangeType(b, typeof(UInt32))) ? a : b;

                    else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                        result = ((Int32)Convert.ChangeType(a, typeof(Int32)) > 
                                    (Int32)Convert.ChangeType(b, typeof(Int32))) ? a : b;

                    else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                        result = ((UInt16)Convert.ChangeType(a, typeof(UInt16)) > 
                                    (UInt16)Convert.ChangeType(b, typeof(UInt16))) ? a : b;

                    else if (aType == TypeCode.Char || bType == TypeCode.Char)
                        result = ((Char)Convert.ChangeType(a, typeof(Char)) > 
                                    (Char)Convert.ChangeType(b, typeof(Char))) ? a : b;

                    else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                        result = ((Int16)Convert.ChangeType(a, typeof(Int16)) > 
                                    (Int16)Convert.ChangeType(b, typeof(Int16))) ? a : b;

                    else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                        result = ((Byte)Convert.ChangeType(a, typeof(Byte)) > 
                                    (Byte)Convert.ChangeType(b, typeof(Byte))) ? a : b;

                    else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                        result = ((SByte)Convert.ChangeType(a, typeof(SByte)) > 
                                    (SByte)Convert.ChangeType(b, typeof(SByte))) ? a : b;
                    else
                        throw new YarrException("Max: arguments must be numeric");
                }
                catch (System.InvalidCastException)
                {
                    throw new YarrException("Max: arguments must be numeric");
                }
            }

            return result;
        }


        /// <summary>
        /// (min number*)
        /// Find the min of a list of number
        /// </summary>
        /// <param name="args">a list of numbers</param>
        /// <param name="environment">The local environment</param>
        /// <returns>The min of the list of numbers</returns>
        [Function("min", "(arg0 &rest args)")]
        public static object Min (Environment environment)
        {
            object result = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null)
                return result;

            foreach (Object number in args)
            {
                try
                {
                    object a = result, b = number;
                    Type ta = Primitives.TypeOf(a);
                    Type tb = Primitives.TypeOf(b);

                    if (a is IComparable  &&  b is IComparable  &&  ta == tb)
                    {
                        result = ((IComparable)a).CompareTo(b) < 0 ? a : b;
                        continue;
                    }

                    TypeCode aType = Type.GetTypeCode(ta);
                    TypeCode bType = Type.GetTypeCode(tb);
                    if (!Primitives.IsNumber(aType) || !Primitives.IsNumber(bType))
                        throw new YarrException("Min: arguments must be numeric");

                    if (aType == TypeCode.Double || bType == TypeCode.Double)
                        result = ((Double)Convert.ChangeType(a, typeof(Double)) < 
                                    (Double)Convert.ChangeType(b, typeof(Double))) ? a : b;

                    else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                        result = ((Decimal)Convert.ChangeType(a, typeof(Decimal)) < 
                                    (Decimal)Convert.ChangeType(b, typeof(Decimal))) ? a : b;

                    else if (aType == TypeCode.Single || bType == TypeCode.Single)
                        result = ((Single)Convert.ChangeType(a, typeof(Single)) < 
                                    (Single)Convert.ChangeType(b, typeof(Single))) ? a : b;

                    else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                        result = ((UInt64)Convert.ChangeType(a, typeof(UInt64)) < 
                                    (UInt64)Convert.ChangeType(b, typeof(UInt64))) ? a : b;

                    else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                        result = ((Int64)Convert.ChangeType(a, typeof(Int64)) < 
                                    (Int64)Convert.ChangeType(b, typeof(Int64))) ? a : b;

                    else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                        result = ((UInt32)Convert.ChangeType(a, typeof(UInt32)) < 
                                    (UInt32)Convert.ChangeType(b, typeof(UInt32))) ? a : b;

                    else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                        result = ((Int32)Convert.ChangeType(a, typeof(Int32)) < 
                                    (Int32)Convert.ChangeType(b, typeof(Int32))) ? a : b;

                    else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                        result = ((UInt16)Convert.ChangeType(a, typeof(UInt16)) < 
                                    (UInt16)Convert.ChangeType(b, typeof(UInt16))) ? a : b;

                    else if (aType == TypeCode.Char || bType == TypeCode.Char)
                        result = ((Char)Convert.ChangeType(a, typeof(Char)) < 
                                    (Char)Convert.ChangeType(b, typeof(Char))) ? a : b;

                    else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                        result = ((Int16)Convert.ChangeType(a, typeof(Int16)) < 
                                    (Int16)Convert.ChangeType(b, typeof(Int16))) ? a : b;

                    else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                        result = ((Byte)Convert.ChangeType(a, typeof(Byte)) < 
                                    (Byte)Convert.ChangeType(b, typeof(Byte))) ? a : b;

                    else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                        result = ((SByte)Convert.ChangeType(a, typeof(SByte)) < 
                                    (SByte)Convert.ChangeType(b, typeof(SByte))) ? a : b;
                    else
                        throw new YarrException("Min: arguments must be numeric");
                }
                catch (System.InvalidCastException)
                {
                    throw new YarrException("Min: arguments must be numeric");
                }
            }
            return result;
        }


        /// <summary>
        /// (mod a b)
        /// Computes a (mod b)
        /// </summary>
        /// <param name="args">A list of two integral types</param>
        /// <param name="environment">The local environment</param>
        /// <returns>a (mod b)</returns>
        [Function("mod;%", "(arg0 &rest args)")]
        public static Object Mod(Environment environment)
        {
            object result = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null)
                return result;

            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType;
            TypeCode bType  = TypeCode.Empty;

			foreach (Object number in args) 
			{
                aType = result == null ? TypeCode.Empty : Type.GetTypeCode(Primitives.TypeOf(result));
                bType = Type.GetTypeCode(Primitives.TypeOf(number));

                if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                    result = ModHelper(result, number, aType, bType);
                else
                {
                    Type t0 = Primitives.TypeOf(result);
                    Type t1 = Primitives.TypeOf(number);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_modulus", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "Mod: No modulus operator defined for ({0},{1})", t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = result;
                    a[1] = number;

                    result = mi.Invoke(t[0], a);
                }
			}

			return result;
		}

        private static Object ModHelper(object a, object b, TypeCode aType, TypeCode bType)
        {
            try
            {
                if (aType == TypeCode.Double || bType == TypeCode.Double)
                    return (Double)Convert.ChangeType(a, typeof(Double)) % 
                           (Double)Convert.ChangeType(b, typeof(Double));

                else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal) 
                    return (Decimal)Convert.ChangeType(a, typeof(Decimal)) % 
                           (Decimal)Convert.ChangeType(b, typeof(Decimal));

                else if (aType == TypeCode.Single || aType == TypeCode.Single)
                    return (Single)Convert.ChangeType(a, typeof(Single)) % 
                           (Single)Convert.ChangeType(b, typeof(Single));
                
                else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                    return (UInt64)Convert.ChangeType(a, typeof(UInt64)) % 
                           (UInt64)Convert.ChangeType(b, typeof(UInt64));

                else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                    return (Int64)Convert.ChangeType(a, typeof(Int64)) % 
                           (Int64)Convert.ChangeType(b, typeof(Int64));

                else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                    return (UInt32)Convert.ChangeType(a, typeof(UInt32)) % 
                           (UInt32)Convert.ChangeType(b, typeof(UInt32));

                else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                    return (Int32)Convert.ChangeType(a, typeof(Int32)) % 
                           (Int32)Convert.ChangeType(b, typeof(Int32));

                else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                    return (UInt16)((UInt32)Convert.ChangeType(a, typeof(UInt32)) %
                                    (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Char || bType == TypeCode.Char)
                    return (Char)((UInt32)Convert.ChangeType(a, typeof(UInt32)) %
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                    return (Int16)((Int32)Convert.ChangeType(a, typeof(Int32)) %
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                    return (SByte)((UInt32)Convert.ChangeType(a, typeof(UInt32)) %
                                   (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                    return (SByte)((Int32)Convert.ChangeType(a, typeof(Int32)) %
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else
                    throw new YarrException("Mod: arguments must be numeric");
            }
            catch (System.InvalidCastException)
            {
                throw new YarrException("Mod: arguments must be numeric");
            }
        }


		/// <summary>
		/// (* number*)
		/// Computes the product of a list of numbers
        /// Each argument must be a numeric type, such as System.In32 or System.Double
        /// </summary>
		/// <param name="args">A list of numbers</param>
		/// <param name="environment">The local environment</param>
		/// <returns>The product of the numbers. The return type is dependent on the arguments</returns>
		[Function("*", "(&rest args)")]
        public static Object Multiply(Environment environment) 
		{
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null)
                return 1;

            if (args.Length == 1)
            {
                if (Primitives.IsNumber(args.First()))
                    return args.First();
                else
                    throw new YarrException("Multiply: Arguments must be numeric");
            }

            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType;
            TypeCode bType  = TypeCode.Empty;

            object result = args.First();

            foreach (Object number in (Cons)args.Rest())
            {
                aType = result == null ? TypeCode.Empty : Type.GetTypeCode(Primitives.TypeOf(result));
                bType = Type.GetTypeCode(Primitives.TypeOf(number));

                if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                    result = MultiplyHelper(result, number, aType, bType);
                else
                {
                    Type t0 = Primitives.TypeOf(result);
                    Type t1 = Primitives.TypeOf(number);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_multiply", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "Multiply: No multiplication operator defined for ({0},{1})", t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = result;
                    a[1] = number;

                    result = mi.Invoke(t[0], a);
                }
            }

            return result;
        }

        private static Object MultiplyHelper (object a, object b, TypeCode aType, TypeCode bType)
        {
            try
            {
                if (aType == TypeCode.Double || bType == TypeCode.Double)
                    return (Double)Convert.ChangeType(a, typeof(Double)) * 
                           (Double)Convert.ChangeType(b, typeof(Double));

                else if (aType == TypeCode.Single || bType == TypeCode.Single)
                    return (Single)Convert.ChangeType(a, typeof(Single)) * 
                           (Single)Convert.ChangeType(b, typeof(Single));

                else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                    return (Decimal)Convert.ChangeType(a, typeof(Decimal)) *
                           (Decimal)Convert.ChangeType(b, typeof(Decimal));

                else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                    return (UInt64)Convert.ChangeType(a, typeof(UInt64)) * 
                           (UInt64)Convert.ChangeType(b, typeof(UInt64));

                else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                    return (Int64)Convert.ChangeType(a, typeof(Int64)) * 
                           (Int64)Convert.ChangeType(b, typeof(Int64));

                else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                    return (UInt32)Convert.ChangeType(a, typeof(UInt32)) * 
                           (UInt32)Convert.ChangeType(b, typeof(UInt32));

                else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                    return (Int32)Convert.ChangeType(a, typeof(Int32)) * 
                           (Int32)Convert.ChangeType(b, typeof(Int32));

                else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                    return (UInt16)((UInt32)Convert.ChangeType(a, typeof(UInt32)) *
                                    (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Char || bType == TypeCode.Char)
                    return (Char)((UInt32)Convert.ChangeType(a, typeof(UInt32)) *
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                    return (Int16)((Int32)Convert.ChangeType(a, typeof(Int32)) *
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                    return (Byte)((UInt32)Convert.ChangeType(a, typeof(UInt32)) *
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                    return (SByte)((Int32)Convert.ChangeType(a, typeof(Int32)) *
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else
                    throw new YarrException("Multiply: arguments must be numeric");
            }
            catch (System.InvalidCastException)
            {
                throw new YarrException("Multiply: arguments must be numeric");
            }
        }


		/// <summary>
		/// (- number*)
        /// Performs subtraction on a list of numbers
        /// Each argument must be a numeric type, such as System.In32 or System.Double
        /// </summary>
		/// <param name="args">A list of numbers</param>
		/// <param name="environment">The local environment</param>
		/// <returns>The result of subtracting every subsequent number from the first number.
        /// The return type is dependent on the arguments</returns>
        [Function("-", "(arg0 &rest args)")]
        public static Object Subtract (Environment environment)
        {
            object result = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType;
            TypeCode bType  = Type.GetTypeCode(Primitives.TypeOf(result));

            if (args == null)
            {
                if (Primitives.IsNumber(bType))
                    return SubtractHelper((Int16)0, result, TypeCode.Int16, bType);
                else
                {
                    t = new Type[1];
                    t[0] = Primitives.TypeOf(result);

                    mi = MethodCache.FindMethod("op_unarynegation", t[0], t, null,
                                            BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "Subtract: No unary negation operator defined for type {0}", t[0]));

                    a = new Object[1];
                    a[0] = result;
                    return mi.Invoke(t[0], a);
                }
            }

            foreach (Object number in args)
            {
                aType = Type.GetTypeCode(Primitives.TypeOf(result));
                bType = Type.GetTypeCode(Primitives.TypeOf(number));

                if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                    result = SubtractHelper(result, number, aType, bType);
                else
                {
                    Type t0 = Primitives.TypeOf(result);
                    Type t1 = Primitives.TypeOf(number);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_subtraction", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "Subtract: No subtraction operator defined for ({0},{1})", t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = result;
                    a[1] = number;

                    result = mi.Invoke(t[0], a);
                }
            }

            return result;
        }

        internal static Object SubtractHelper (object a, object b, TypeCode aType, TypeCode bType)
        {
            try
            {
                if (aType == TypeCode.Double || bType == TypeCode.Double)
                    return (Double)Convert.ChangeType(a, typeof(Double)) - 
                           (Double)Convert.ChangeType(b, typeof(Double));

                else if (aType == TypeCode.Single || bType == TypeCode.Single)
                    return (Single)Convert.ChangeType(a, typeof(Single)) - 
                           (Single)Convert.ChangeType(b, typeof(Single));

                else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                    return (Decimal)Convert.ChangeType(a, typeof(Decimal)) -
                           (Decimal)Convert.ChangeType(b, typeof(Decimal));

                else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                    return (UInt64)Convert.ChangeType(a, typeof(UInt64)) - 
                           (UInt64)Convert.ChangeType(b, typeof(UInt64));

                else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                    return (Int64)Convert.ChangeType(a, typeof(Int64)) - 
                           (Int64)Convert.ChangeType(b, typeof(Int64));

                else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                    return (UInt32)Convert.ChangeType(a, typeof(UInt32)) - 
                           (UInt32)Convert.ChangeType(b, typeof(UInt32));

                else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                    return (Int32)Convert.ChangeType(a, typeof(Int32)) - 
                           (Int32)Convert.ChangeType(b, typeof(Int32));

                else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                    return (UInt16)((UInt32)Convert.ChangeType(a, typeof(UInt32)) -
                                    (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Char || bType == TypeCode.Char)
                    return (Char)((UInt32)Convert.ChangeType(a, typeof(UInt32)) -
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                    return (Int16)((Int32)Convert.ChangeType(a, typeof(Int32)) -
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                    return (Byte)((UInt32)Convert.ChangeType(a, typeof(UInt32)) -
                                  (UInt32)Convert.ChangeType(b, typeof(UInt32)));

                else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                    return (SByte)((Int32)Convert.ChangeType(a, typeof(Int32)) -
                                   (Int32)Convert.ChangeType(b, typeof(Int32)));

                else
                    throw new YarrException("Subtract: arguments must be numeric");
            }
            catch (System.InvalidCastException)
            {
                throw new YarrException("Subtract: arguments must be numeric");
            }
		}
	}
}
